#pragma once

#include "vector.h"
#include "matrix.h"

namespace g2 {

template <typename T>
class basic_affine;
//can be constructed from
template <typename T>
class basic_translation;
template <typename T>
class basic_rigid;
template <typename T>
class basic_rigid_scale;

typedef basic_affine<double> affine;
typedef basic_affine<float> affinef;
typedef basic_affine<long double> affinel;

// Affine transform of the plane
template <class T>
class basic_affine
{
public:
	typedef T element_type;
public:
	// p_new = r(p_old) + d
	basic_matrix<T> r;
	basic_vector<T> d; 
public:
	basic_affine() 
		{ }
	template <class U>
	basic_affine(basic_matrix<U> const & matrix, basic_vector<U> const & displacement) : r(matrix), d(displacement)
		{ }
	template <class U>
  basic_affine(basic_translation<U> const & t) : r(1, 0, 0, 1), d(t.d)
		{ }
	template <class U>
  basic_affine(basic_rigid<U> const & t) : r(t.r.cos(), -t.r.sin(), t.r.sin(), t.r.cos()), d(t.d)
		{ }
	template <class U>
  basic_affine(basic_rigid_scale<U> const & t) : r(t.r.p, -t.r.q, t.r.q, t.r.p), d(t.d)
		{ }
	basic_affine & identity() 
		{ r.identity(); d.zero(); return * this; }

	friend bool operator == (const basic_affine<T> & lhs, const basic_affine<T> & rhs) 
		{ return lhs.r == rhs.r && lhs.d == rhs.d; }
	friend bool operator != (const basic_affine<T> & lhs, const basic_affine<T> & rhs) 
		{ return !(lhs == rhs); }

	basic_vector<T> operator ()(basic_vector<T> const & p_old) const 
		{ return r(p_old) + d;	};
	basic_vector<T> inverse(basic_vector<T> const & p_new) const
		{ return r.inverse(p_new - d); }

	basic_affine<T> inverse() const {
		basic_matrix<T> i = r.inverse();
		return basic_affine<T>(i, -i(d)); 
	}

	friend basic_affine<T> operator * (
		const basic_affine<T> & a1, const basic_affine<T> & a0) 
	{ return basic_affine<T>(a1.r * a0.r, a1.d + a1.r(a0.d)); }

  /// modifies the transformation so that it remains valid after scaling of all points in s times
  basic_affine & scale(T s)
    { d *= s; return * this; }

  static const char * name() { return "affine"; }
};

} //namespace g2
